use actix::prelude::*;
use actix_web::{web, Error, HttpRequest, HttpResponse};
use actix_web_actors::ws;
use serde::Deserialize;
use std::time::{Duration, Instant};

use crate::services::price_service::PriceService;

pub async fn ws_route(
    req: HttpRequest,
    stream: web::Payload,
    svc: web::Data<PriceService>,
) -> Result<HttpResponse, Error> {
    let actor = PriceWsActor::new(svc.get_ref().clone());
    ws::start(actor, &req, stream)
}

#[derive(Deserialize, Debug, Clone)]
struct SubscribeMsg {
    symbol: String,
}

struct PriceWsActor {
    hb: Instant,
    svc: PriceService,
    symbol: Option<String>,
}

impl PriceWsActor {
    fn new(svc: PriceService) -> Self {
        Self {
            hb: Instant::now(),
            svc,
            symbol: None,
        }
    }

    fn start_heartbeat(&self, ctx: &mut ws::WebsocketContext<Self>) {
        ctx.run_interval(Duration::from_secs(10), |act, ctx| {
            // ping client
            if Instant::now().duration_since(act.hb) > Duration::from_secs(30) {
                ctx.stop();
                return;
            }
            ctx.ping(b"ping");
        });
    }

    fn start_ticker(&self, ctx: &mut ws::WebsocketContext<Self>) {
        ctx.run_interval(Duration::from_secs(1), |act, ctx| {
            if let Some(sym) = &act.symbol {
                let price = act.svc.tick(sym);
                let msg = serde_json::json!({
                    "type": "price_update",
                    "data": price
                });
                ctx.text(msg.to_string());
            }
        });
    }
}

impl Actor for PriceWsActor {
    type Context = ws::WebsocketContext<Self>;

    fn started(&mut self, ctx: &mut Self::Context) {
        self.start_heartbeat(ctx);
        self.start_ticker(ctx);
    }
}

impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for PriceWsActor {
    fn handle(&mut self, item: Result<ws::Message, ws::ProtocolError>, ctx: &mut Self::Context) {
        match item {
            Ok(ws::Message::Ping(msg)) => {
                self.hb = Instant::now();
                ctx.pong(&msg);
            }
            Ok(ws::Message::Pong(_)) => {
                self.hb = Instant::now();
            }
            Ok(ws::Message::Text(txt)) => {
                // 期望 {"symbol":"AAPL"}
                if let Ok(req) = serde_json::from_str::<SubscribeMsg>(&txt) {
                    self.symbol = Some(req.symbol.to_uppercase());
                    ctx.text(serde_json::json!({"type":"subscribed","symbol": self.symbol}).to_string());
                } else {
                    ctx.text(serde_json::json!({"type":"error","message":"invalid subscribe message"}).to_string());
                }
            }
            Ok(ws::Message::Close(reason)) => {
                ctx.close(reason);
                ctx.stop();
            }
            _ => {}
        }
    }
}